Skip to content

fix(a2a): add Agent Card JWS signature verification and trust policy - #6202

Merged
bug-ops merged 2 commits into
mainfrom
fix/5928-a2a-card-signing
Jul 13, 2026
Merged

fix(a2a): add Agent Card JWS signature verification and trust policy#6202
bug-ops merged 2 commits into
mainfrom
fix/5928-a2a-card-signing

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds optional, feature-gated (card-signing) A2A 1.0.0 AgentCardSignature (JWS/ES256) verification to AgentRegistry::discover(), closing the unauthenticated Agent Card trust gap described in research(a2a): no Agent Card signing/verification — protocol version stuck at 0.2.1 vs. current spec 1.0.0 #5928: peer cards were previously accepted with no signature check and no cross-check between the queried base_url and the card's own url field.
  • Adds a tri-state card_trust_policy (ignore/prefer/require, default ignore) that combines signature verification and URL-origin consistency via a most-severe-wins precedence, backed by an out-of-band operator-configured trusted-key store ([a2a_client].trusted_agent_keys) rather than the card-supplied jku URL, which would reopen an SSRF surface this crate already guards against.
  • A2A_PROTOCOL_VERSION intentionally stays at "0.2.1" rather than claiming full 1.0.0 conformance — this PR adds one 1.0.0 feature (signatures), not the full spec delta. Well-known path rename, transport bindings, and method-name reconciliation remain unimplemented and are documented as deferred.
  • Config validation fails fast at load time if require is set without the card-signing feature compiled in, rather than silently degrading.

Known limitations (tracked as follow-ups, not silently deferred)

  • Canonicalization is implemented per the A2A spec §8.4 text but not validated against a real a2a-sdk signed-card vector — no network access to the reference implementation during development. require/prefer may reject genuinely valid peers until this is proven; a tracing::warn! fires at with_trust() call time to surface this to operators. Tracked in validate A2A card signature JWS canonicalization against a real signed-card vector #6201.
  • AgentRegistry has no runtime construction site anywhere in this codebase (verified independently by multiple review passes) — card_trust_policy/trusted_agent_keys are fully implemented and tested as a library + config primitive, but nothing in the running agent wires them up yet, so require currently enforces nothing live. Tracked in wire AgentRegistry construction site so card_trust_policy actually enforces #6200.
  • No --init wizard or CLI flag for the new [a2a_client] knobs — there is no existing precedent for the sibling require_tls/ssrf_protection fields either (TOML + env only), so none was invented here.

Review process

This went through the full architecture → critique → implementation → parallel validation (tests/perf/security/adversarial critique) → code review pipeline:

  • Architecture critic pass found 5 significant gaps (raw-JSON canonicalization vs. typed-struct reserialization, policy axis-combination precedence, require fail-fast, default policy, CI feature wiring) — all folded into the implementation.
  • Implementation critic pass found one concrete bug (multi-signature verification was order-dependent — a tampered-but-trusted signature earlier in the array could veto a later valid one, breaking key-rotation scenarios) — fixed with a regression test pinning order-independence in both directions.
  • Security audit: PASS, no critical/high findings; a handful of low/informational findings already reflected in code comments and the two follow-up issues above.
  • Code review: one round of changes requested (config docs for the new keys/env var were missing from config/default.toml, the setup-guide skill, and both docker-compose files) — fixed and re-approved.

Test plan

  • cargo nextest run -p zeph-a2a --features ibct,server,card-signing — 207 passed
  • cargo nextest run -p zeph-a2a (no features) — 124 passed
  • cargo nextest run -p zeph-config --features card-signing — 778 passed
  • cargo nextest run -p zeph-config (no features) — 773 passed
  • cargo nextest run --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — full CI feature set green
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings — clean
  • cargo +nightly fmt --check — clean
  • Rustdoc gate + cargo test --doc (workspace + per-crate zeph-a2a/zeph-config) — clean
  • cargo deny check — advisories/bans/licenses/sources all ok for new deps (p256, serde_json_canonicalizer); no openssl-sys pulled in
  • gitleaks protect --staged — clean
  • New unit/integration tests cover: legacy unsigned-card backward compat, tampered signature/payload rejection, unknown kid, every policy × axis severity combination, config-load fail-fast, migration idempotency, order-independent multi-signature verification
  • Testing playbook (.local/testing/playbooks/a2a.md) and coverage-status row added — status Untested pending a live session (no runtime consumer yet per wire AgentRegistry construction site so card_trust_policy actually enforces #6200)

Closes #5928

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate dependencies Dependency updates config Configuration file changes size/XL Extra large PR (500+ lines) and removed bug Something isn't working labels Jul 13, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 13, 2026 01:44
Add optional, feature-gated (card-signing) A2A 1.0.0 AgentCardSignature
verification to AgentRegistry::discover(), closing the card-spoofing/
impersonation gap described in #5928: peer cards were previously trusted
unauthenticated with no cross-check between the queried base_url and the
card's own url field.

Adds a tri-state card_trust_policy (ignore/prefer/require, default
ignore) combining signature verification and URL-origin consistency via
a most-severe-wins precedence, an out-of-band operator-configured
trusted-key store (not the card-supplied jku, which would reopen an SSRF
surface this crate already guards against), and a config-load fail-fast
when require is set without the card-signing feature compiled in.

A2A_PROTOCOL_VERSION intentionally stays at 0.2.1 rather than claiming
full 1.0.0 conformance, since this adds one 1.0.0 feature (signatures),
not the full spec delta (transport bindings, well-known path rename,
method reconciliation all remain unimplemented and are documented as
deferred).

Known limitations, tracked as follow-ups:
- Canonicalization is implemented per spec text but not validated
  against a real a2a-sdk signed-card vector (no network access during
  development); require may reject genuinely valid peers until this is
  proven. Tracked in #6201.
- AgentRegistry has no runtime construction site in this codebase yet,
  so card_trust_policy is a fully implemented and tested library/config
  primitive with no live consumer. Tracked in #6200.

Closes #5928
@bug-ops
bug-ops force-pushed the fix/5928-a2a-card-signing branch from 7ce3b80 to 656411e Compare July 13, 2026 01:44
@github-actions github-actions Bot added the bug Something isn't working label Jul 13, 2026
…spec

Update specs/014-a2a/spec.md to reflect the Agent Card signature
verification and CardTrustPolicy work landed in this PR (#5928):
policy outcome table, the two-enum protocol/config split rationale,
and a new Key Invariant codifying that the signature trust anchor
must be an out-of-band key store, never a card-supplied jku, so a
future contributor doesn't "fix" that as an oversight.

Also adds an A2A 1.0.0 Conformance section explaining why
A2A_PROTOCOL_VERSION stays at 0.2.1 and documenting the two known
gaps (#6200 unwired registry, #6201 unvalidated interop) as current
limitations, so the spec doesn't imply more is enforced than it is.
Updates the specs/README.md index entry accordingly.
@bug-ops
bug-ops merged commit 811d1b5 into main Jul 13, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/5928-a2a-card-signing branch July 13, 2026 02:02
bug-ops added a commit that referenced this pull request Jul 13, 2026
The rebase onto origin/main picked up PR #6202's migration step 82
(A2A card trust). Renumber the worktree-quota step to 83 and fix the
now-stale hardcoded step counts and step-order doc comments the merge
left behind.
bug-ops added a commit that referenced this pull request Jul 13, 2026
The rebase onto origin/main picked up PR #6202's migration step 82
(A2A card trust). Renumber the worktree-quota step to 83 and fix the
now-stale hardcoded step counts and step-order doc comments the merge
left behind.
bug-ops added a commit that referenced this pull request Jul 13, 2026
The rebase onto origin/main picked up PR #6202's migration step 82
(A2A card trust). Renumber the worktree-quota step to 83 and fix the
now-stale hardcoded step counts and step-order doc comments the merge
left behind.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working config Configuration file changes core zeph-core crate dependencies Dependency updates documentation Improvements or additions to documentation rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

research(a2a): no Agent Card signing/verification — protocol version stuck at 0.2.1 vs. current spec 1.0.0

1 participant